from enum import Enum
from exception import UnsupportCircuitException

class Wire():
    def __init__(self, bit_level = -1, value = None) -> None:
        self.bit_level = bit_level
        self.value = value
        self.input_circuit = None
        self.output_circuits = []
    
    def __repr__(self) -> str:
        match self.value:
            case None:  return 'xxx'
            case True:  return '==='
            case False: return '---'

class Circuit():
    def __init__(self, wires : dict[str, Wire], input_wire_names : set[str], output_wire_names : set[str]) -> None:
        self.evaluated = False

        self.input_wires : dict[str, Wire] = {}
        self.output_wires : dict[str, Wire] = {}

        for wire_name in wires:
            if wire_name in input_wire_names:
                self.input_wires[wire_name] = wires[wire_name]
            elif wire_name in output_wire_names:
                self.output_wires[wire_name] = wires[wire_name]

        for wire in self.input_wires.values():
            wire.output_circuits.append(self)
        
        for wire in self.output_wires.values():
            wire.input_circuit = self

    def input_value(self, port_name : str):
        return self.input_wires[port_name].value

    def can_evaluate(self) -> bool:
        for wire in self.input_wires.values():
            if wire.value == None:
                return False
        return True

    def do_evaluate(self) -> None:
        pass

    def evaluate(self) -> bool:
        if self.can_evaluate() == False:
            return False
        else:
            self.do_evaluate()
            self.evaluated = True
            return True

class InputStatus(Enum):
    UNSURE = 0
    RIGHT = 1
    INVERT = 2

class FA(Circuit):
    """
        S = A ^ B ^ CI
        CO = A * B + CI * (A | B)
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A', 'B', "CI"}, {'CO', 'S'})
    
        self.name = name

        self.S_opt = False
        self.CO_opt = False
        self.input_status = InputStatus.UNSURE

        self.CO_must_be_right = False

    def do_evaluate(self) -> None:
        A = self.input_value('A')
        B = self.input_value('B')
        CI = self.input_value('CI')
        
        self.output_wires['S'].value = (A ^ B ^ CI)
        self.output_wires['CO'].value = ((A and B) or (CI and (A or B)))

        if self.S_opt == True:
            self.output_wires['S'].value = not self.output_wires['S'].value
        if self.CO_opt == True:
            self.output_wires['CO'].value = not self.output_wires['CO'].value
    
    def get_level(self) -> int:
        if self.input_wires['A'].bit_level != -1:
            return self.input_wires['A'].bit_level
        elif self.input_wires['B'].bit_level != -1:
            return self.input_wires['B'].bit_level
        elif self.input_wires['CI'].bit_level != -1:
            return self.input_wires['CI'].bit_level
        elif self.output_wires['S'].bit_level != -1:
            return self.input_wires['S'].bit_level
        elif self.output_wires['CO'].bit_level != -1:
            return self.input_wires['CO'].bit_level - 1
        return -1

    def __repr__(self) -> str:
        return '[name: {0}, level {1}, input_status: {2}, CO_must_be_right: {3}, S_opt: {4}, CO_opt: {5}]'.format(
            self.name,
            self.get_level(),
            self.input_status,
            self.CO_must_be_right,
            self.S_opt,
            self.CO_opt
        )
    
class INV(Circuit):
    """
        ZN = ~I [pass]
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'I'}, {'ZN'})

    def do_evaluate(self) -> None:
        I = self.input_value('I')
        self.output_wires['ZN'].value = (not I)

class NOR2(Circuit):
    """
        ZN = ~(A1 | A2) [pass]
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        self.output_wires['ZN'].value = (not (A1 or A2))

class NAND2(Circuit):
    """
       ZN = ~ (A1 & A2) [pass]
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        self.output_wires['ZN'].value = (not (A1 and A2))

class AND2(Circuit):
    """
        ZN = A1 & A2
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        self.output_wires['ZN'].value = (A1 and A2)

class AN2(Circuit):
    """
        Z = A1 & A2
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2'}, {'Z'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        self.output_wires['Z'].value = (A1 and A2)

class OR2(Circuit):
    """
        ZN = A1 | A2
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        self.output_wires['ZN'].value = (A1 or A2)
    

class AOI21(Circuit):
    """
        ZN = ~ ((A1 & A2) | B) [pass]
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2', 'B'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        B = self.input_value('B')
        self.output_wires['ZN'].value = (not ((A1 and A2) or B))

class OAI31(Circuit):
    """
        ZN = ~((A1 | A2 | A3) & B)
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2', 'A3', 'B'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        A3 = self.input_value('A3')
        B = self.input_value('B')
        self.output_wires['ZN'].value = (not ((A1 or A2 or A3) and B))

class IAO21(Circuit):
    """
        ZN =  not ((~A1 & ~A2) | B) [pass]
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2', 'B'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        B = self.input_value('B')
        self.output_wires['ZN'].value = not (((not A1) and (not A2)) or B)

class OA21(Circuit):
    """
        Z = (A1 | A2) & B [pass]
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2', 'B'}, {'Z'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        B = self.input_value('B')
        self.output_wires['Z'].value = ((A1 or A2) and B)

class OAI21(Circuit):
    """
        ZN = ~ ((A1 | A2) & B)
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2', 'B'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        B = self.input_value('B')
        self.output_wires['ZN'].value = (not ((A1 or A2) and B))

class NAND3(Circuit):
    """
        ZN = ~ (A1 & A2 & A3)
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A1', 'A2', 'A3'}, {'ZN'})

    def do_evaluate(self) -> None:
        A1 = self.input_value('A1')
        A2 = self.input_value('A2')
        A3 = self.input_value('A3')
        self.output_wires['ZN'].value = (not (A1 and A2 and A3))

class MAOI222(Circuit):
    """
        ZN = ~ ((A & B) | (B & C) | (A & C))
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'A', 'B', 'C'}, {'ZN'})

    def do_evaluate(self) -> None:
        A = self.input_value('A')
        B = self.input_value('B')
        C = self.input_value('C')
        self.output_wires['ZN'].value = (not ((A and B) or (B and C) or (A and C)))

class REG(Circuit):
    """
        Q = D
    """
    def __init__(self, name: str, wires: dict[str, Wire]) -> None:
        super().__init__(wires, {'D'}, {'Q'})

    def do_evaluate(self) -> None:
        self.output_wires['Q'].value = self.input_value('D')

class CircuitFactory():

    creator = {
        'FA1D0BWP30P140HVT' : FA,
        'NR2D0BWP30P140HVT' : NOR2,
        'CKND2D1BWP30P140HVT' : NAND2,
        'ND2D0P7BWP30P140HVT' : NAND2,
        'CKAN2D1BWP30P140HVT' : AN2,
        'NR2OPTIBD1BWP30P140HVT' : NOR2,
        'OA21D0BWP30P140HVT' : OA21,
        'INVD0BWP30P140HVT' : INV,
        'AOI21D0BWP30P140HVT' : AOI21,
        'AOI21D1BWP30P140HVT' : AOI21,
        'DFCNQD1BWP30P140HVT' : REG,
        'ND2D0BWP30P140HVT' : NAND2,
        'ND3D0P7BWP30P140HVT' : NAND3,
        'AOI21D0P7BWP30P140HVT' : AOI21,
        'FA1D1BWP30P140HVT' : FA,
        'AN2D1BWP30P140HVT' : AN2,
        'AN2D0BWP30P140HVT' : AN2,
        'IAO21D0BWP30P140HVT' : IAO21,
        'IAO21D1BWP30P140HVT' : IAO21,
        'OAI21D0P7BWP30P140HVT' : OAI21,
        'OAI31D0BWP30P140HVT' : OAI31,
        'MAOI222D0BWP30P140HVT' : MAOI222
    }

    def create_circuit(type : str, name : str, wires : dict[str, Wire]):
        circuit_type = CircuitFactory.creator.get(type)
        if circuit_type != None:
            return CircuitFactory.creator[type](name, wires)
        raise UnsupportCircuitException(type)